package com.yongnuo.remoteconlight;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import com.yongnuo.bluetooth.SampleGattAttributes;
import com.yongnuo.remoteconlight.BluetoothLeServiceV2.BluetoothDeviceInfoPool.BleDeviceInfo;

import android.R.bool;
import android.R.integer;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.bluetooth.BluetoothGattService;


public class ConnectionManager
{
    public static final int CONNECT_DEVICE_NOT_FOUND = -1;
    public static final int CONNECT_INIT_FAIL = -2;
    public static final int CONNECT_DEVICE_ADDRESS_ERROR = -3;    
    
    private final String TAG = "zbble";
    private BluetoothLeServiceV2 mBluetoothLeService = null;
    //
    private Context mContext = null;
    private BluetoothAdapter mBluetoothAdapter;
    private LightBleConnectCallback mBluethCallback = null;
    private Handler mHandler = new Handler();
    //
    private ArrayList<String> mLightDevice = new ArrayList<String>();;
    private ArrayList<String> mLightDeviceUnUse = new ArrayList<String>();
    //
    private boolean IsScanning = false;
    
    private final boolean DEBUG = false;
    private void logd(String str) {
	if(DEBUG) {
	    Log.d(TAG, "ConnectionManager: " + str);
	}	
    } 
    private void loge(String str) {
	Log.e(TAG, "ConnectionManager: " + str);
    } 
    
    class BleConnectInfo {
	public static final int CONNECTING_SETUP_INIT           = 0;
	public static final int CONNECTING_SETUP_PAIRED         = 1;
	public static final int CONNECTING_SETUP_SERVICE_FOUNED = 2;
	public static final int CONNECTING_SETUP_DISCONNECING   = 3;	
	public int CONNECTING_SETUP = CONNECTING_SETUP_INIT;
	public BleConnectInfo(String address) {
	    // TODO Auto-generated constructor stub
	    this.address = address;
	}
        //
//        public void SetConnectState(int state)
//        {
//            switch(state)
//            {
//                case BluetoothLeServiceV2.STATE_CONNECTED:
//                    this.HAS_CONNECTED       = true;
//                    this.HAS_DISCOVERED      = true;
//                    this.HAS_SERVICE_FOUNDED = true;
//                    break;
//
//                case BluetoothLeServiceV2.STATE_CONNECTING:
//                    break;
//
//                case BluetoothLeServiceV2.STATE_DISCONNECTING:
//                    this.HAS_CONNECTED       = false;       
//                    this.HAS_DISCOVERED      = false;      
//                    this.HAS_SERVICE_FOUNDED = false; 
//                    break;
//    
//                case BluetoothLeServiceV2.STATE_DISCONNECTED:
//                    this.HAS_CONNECTED       = false;       
//                    this.HAS_DISCOVERED      = false;      
//                    this.HAS_SERVICE_FOUNDED = false; 
//                    this.input               = null;
//                    this.output              = null;
//                    break;   
//            }
//        }
    	public int ConnectState = BluetoothLeServiceV2.STATE_DISCONNECTED;
    	public String address;
    	public BluetoothGattCharacteristic input, output;
    }
    
    private ArrayList<BleConnectInfo> mConnectInfos;
    private BleConnectInfo GetConnectInfoByAddress(String address)
    {
	synchronized (mConnectInfos) {
	    for(BleConnectInfo bc: mConnectInfos)
	    {
		if(bc.address.equals(address))
		{
		    return bc;
		}
	    }
	}
	
	return null;
    }
    
    
    
    private BleConnectInfo mTargetConnectInfo = null;
    public String GetConnectAddress() {
	if(mTargetConnectInfo != null)
	{
	    return mTargetConnectInfo.address;
	}
	return null;
    }
    
    public int GetConnectionState() {
	
	if(mTargetConnectInfo != null)
	{
	    return mTargetConnectInfo.ConnectState;
	}
	
	return BluetoothLeServiceV2.STATE_DISCONNECTED;
    }
    
    ConnectionManager(Context ctx, BluetoothAdapter mAdapter, LightBleConnectCallback mCallBack) {
        mBluetoothAdapter = mAdapter;
        mBluethCallback   = mCallBack;    
        mContext          = ctx;
        logd("Create");
    }
    
    @Override
    protected void finalize() throws Throwable
    {
	// TODO Auto-generated method stub
	super.finalize();
	logd("finalize");
	for(BleConnectInfo ci: mConnectInfos)
	{
	    if(ci.ConnectState == BluetoothLeServiceV2.STATE_CONNECTED
		    || ci.ConnectState == BluetoothLeServiceV2.STATE_CONNECTING)
	    {
		loge("bug!! [ " + ci.address + " ] still connected");
	    }
	}
    }

    boolean GetBleService() {
	if(mContext == null)
	{
	    loge("GetBleService: mContext is null");
	    return false;
	}
	logd("GetBleService");
	mConnectInfos = new ArrayList<ConnectionManager.BleConnectInfo>();
	Intent gattServiceIntent = new Intent(mContext, BluetoothLeServiceV2.class);
	mContext.registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
	return mContext.bindService(gattServiceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }
   
    void ReleaseBleService() {
	logd("ReleaseBleService");
	for(BleConnectInfo ci: mConnectInfos)
	{
	    if(ci.ConnectState == BluetoothLeServiceV2.STATE_CONNECTED
	    || ci.ConnectState == BluetoothLeServiceV2.STATE_CONNECTING)
	    {
		/* here may cause bugs, i not sure call close after disconnect immedially is right */
		mBluetoothLeService.disconnect(ci.address);
		mBluetoothLeService.close(ci.address);
	    }
	}
	mContext.unregisterReceiver(mGattUpdateReceiver);
	mContext.unbindService(mServiceConnection);
	mBluetoothLeService = null;
    }
    
    public boolean IsServiceReady()
    {
	if(mBluetoothLeService != null)
	    return true;
	
	return false;
    }
    
    
    class ConnectCallable implements Callable<Integer>{
    	private BleConnectInfo TargetConnectInfo;
    	public ConnectCallable(BleConnectInfo TargetConnectInfo) {
    	    // TODO Auto-generated constructor stub
    	    this.TargetConnectInfo = TargetConnectInfo;
    	}
    	@Override
        public Integer call() throws Exception
        {
        	// TODO Auto-generated method stub
            if(TargetConnectInfo == null || TargetConnectInfo.address == null)
            {
            	loge("TargetConnectInfo is null or address is null");
            	return BluetoothLeServiceV2.STATE_DISCONNECTED;
            }
                
            Log.d(TAG, "ConnectThread: connect to " + TargetConnectInfo.address);
            BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TargetConnectInfo.address);
            if(device == null)
            {
            	loge("device is null");
            	return BluetoothLeServiceV2.STATE_DISCONNECTED;        
            }
                
            if(!mBluetoothLeService.connect(TargetConnectInfo.address))
            {
            	loge("mBluetoothLeService.connect return false");
            	return BluetoothLeServiceV2.STATE_DISCONNECTED;
            }	        
            //wait for connected
            long time_out = System.currentTimeMillis() + 6000;
            while(System.currentTimeMillis() < time_out)
            {
            	try {
            	    Thread.sleep(100);
            	} catch (InterruptedException e) {
            	    // TODO Auto-generated catch block
            	    e.printStackTrace();
            	    mBluetoothLeService.disconnect(TargetConnectInfo.address);
            	}
        	
            	/* if a device service is error, it's should be disconnect when boardcast received
            	 * some we need not to care this
            	 */
            	if(TargetConnectInfo.ConnectState == BluetoothLeServiceV2.STATE_CONNECTING)
            	{
            	    if(TargetConnectInfo.CONNECTING_SETUP == BleConnectInfo.CONNECTING_SETUP_SERVICE_FOUNED)
            	    {
            		logd("Success Connect To " + TargetConnectInfo.address);
            		return BluetoothLeServiceV2.STATE_CONNECTED;
            	    }
            	    else if(TargetConnectInfo.CONNECTING_SETUP == BleConnectInfo.CONNECTING_SETUP_DISCONNECING)
            	    {
            	        return BleConnectInfo.CONNECTING_SETUP_DISCONNECING;
            	    }
            	}
            	else
            	{
            	    loge("BUG!! Device has other state when we connecting ConnectState = " + TargetConnectInfo.ConnectState);
            	    mBluetoothLeService.disconnect(TargetConnectInfo.address);
            	    return BluetoothLeServiceV2.STATE_DISCONNECTED;
            	}
            }
            
            mBluetoothLeService.disconnect(TargetConnectInfo.address);
            logd("TimeOut For Connect " + TargetConnectInfo.address);
            return BluetoothLeServiceV2.STATE_DISCONNECTED;
        }    
    }
    
    private static IntentFilter makeGattUpdateIntentFilter()
    {
        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BluetoothLeServiceV2.ACTION_GATT_CONNECTED);
        intentFilter.addAction(BluetoothLeServiceV2.ACTION_GATT_DISCONNECTED);
        intentFilter.addAction(BluetoothLeServiceV2.ACTION_GATT_SERVICES_DISCOVERED);
        intentFilter.addAction(BluetoothLeServiceV2.ACTION_DATA_AVAILABLE);
        return intentFilter;
    }
    

    
    // Handles various events fired by the Service.
    // ACTION_GATT_CONNECTED: connected to a GATT server.
    // ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
    // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
    // ACTION_DATA_AVAILABLE: received data from the device.  This can be a result of read
    //                        or notification operations.
    private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver()
    {
        @Override
        public void onReceive(Context context, Intent intent)
        {
            final String action = intent.getAction();

            logd("receive boardcast = " + action);
            if (BluetoothLeServiceV2.ACTION_GATT_CONNECTED.equals(action))
            {
        	String address = intent.getStringExtra(BluetoothLeServiceV2.DEVICE_ADDRESS);
        	for(BleConnectInfo Coninfo: mConnectInfos)
        	{
        	    if(Coninfo.address.equals(address))
        	    {
        		Coninfo.CONNECTING_SETUP = BleConnectInfo.CONNECTING_SETUP_PAIRED;
//        		Coninfo.ConnectState = BluetoothLeServiceV2.STATE_CONNECTED;
        	    }
        	}
            }
            else if (BluetoothLeServiceV2.ACTION_GATT_DISCONNECTED.equals(action))
            {
        	String address = intent.getStringExtra(BluetoothLeServiceV2.DEVICE_ADDRESS);
        	BleConnectInfo Coninfo = GetConnectInfoByAddress(address);
    		if(Coninfo == mTargetConnectInfo)
    		{
    		    mBluethCallback.OnDisconnect();
    		    mTargetConnectInfo = null;        		    
    		}
    		mConnectInfos.remove(Coninfo);
    		mBluetoothLeService.close(address);
            }
            else if (BluetoothLeServiceV2.ACTION_GATT_SERVICES_DISCOVERED.equals(action))
            {
        	String address = intent.getStringExtra(BluetoothLeServiceV2.DEVICE_ADDRESS);
        	
        	if(CheckIsLightDevice(address, mBluetoothLeService.getSupportedGattServices(address)))
    	    	{
        	    int foundF = 0;
        	    synchronized (mConnectInfos) {
        		for(BleConnectInfo bdinfo: mConnectInfos)
        		{
        		    if(bdinfo.address.equals(address))
        		    {
        			foundF++;
        			if(bdinfo.ConnectState != BluetoothLeServiceV2.STATE_CONNECTING)
        			{
        			    loge("BUG!! we Connect To a Right Device but state error!! it's " + bdinfo.ConnectState);
        			}
        			else if(bdinfo.CONNECTING_SETUP != BleConnectInfo.CONNECTING_SETUP_PAIRED)
        			{
        			    loge("BUG!! we Connect To a Right Device but CONNECT SETP error!!");
        			}else 
        			{
        			    logd("Yes ,we found a right device!");
        			    bdinfo.CONNECTING_SETUP = BleConnectInfo.CONNECTING_SETUP_SERVICE_FOUNED;
        			    mBluetoothLeService.setCharacteristicNotification(address, bdinfo.input, true);
        			}
        		    }
        		}			
        		if(foundF <= 0)
        		{
        		    loge("BUG!!! we Connect To a Right Device but not record! we had to disconnect it!");
        		    mBluetoothLeService.disconnect(address);
        		}
        		else if(foundF > 1)
        		{
        		    loge("BUG!!! we Connect To a Right Device but record more than once!");
        		}
		    }
    	    	}
        	else
        	{//add to unuse able device
        	    BleConnectInfo bConnectInfo = GetConnectInfoByAddress(address);
        	    if(bConnectInfo != null)
        	    {
        		bConnectInfo.ConnectState = BluetoothLeServiceV2.STATE_DISCONNECTING;
        	    }else 
        	    {
			loge("BUG!! could not get address " + address);
		    }
        	    mBluetoothLeService.disconnect(address);
        	    if(!mLightDeviceUnUse.contains(address))
        	    {
        		mLightDeviceUnUse.add(address);
        	    }
	    	}
            }
            else if (BluetoothLeServiceV2.ACTION_DATA_AVAILABLE.equals(action))
            {
        	String address = intent.getStringExtra(BluetoothLeServiceV2.DEVICE_ADDRESS);
                byte[] data = intent.getByteArrayExtra(BluetoothLeServiceV2.EXTRA_DATA);
                
                if(mTargetConnectInfo.address.equals(address))
                {
                    mBluethCallback.OnReceiveData(data);
                }else {
		    loge("BUG!! We Receive data from " + address + " But we not Connect to it!!");
		}
            }
        }
    };
        
    private boolean CheckIsLightDevice(String address, List<BluetoothGattService> gattServices)
    {
        String uuid = null;
        
        if (gattServices == null)
        {
            Log.d(TAG, "gattServices is null");
            return false;
        }

        // Loops through available GATT Services.
        for (BluetoothGattService gattService : gattServices)
        {
            uuid = gattService.getUuid().toString();
            Log.d(TAG, "service uuid = " + uuid);

            BluetoothGattCharacteristic input = 
        	    gattService.getCharacteristic(UUID.fromString(SampleGattAttributes.YONGNUO_YN900_READ_CHARACTERISTIC_UUID));
            
            BluetoothGattCharacteristic output = 
        	    gattService.getCharacteristic(UUID.fromString(SampleGattAttributes.YONGNUO_YN900_WRITE_CHARACTERISTIC_UUID));

            if(input != null && output != null)
            {
                Log.d(TAG, "Service Founded at" + address);
                for(BleConnectInfo bif: mConnectInfos)
                {
                    if(bif.address.equals(address))
                    {
                	bif.input  = input;
                	bif.output = output;
                    }
                }
                return true;
            }
        }
        
        return false;
    }
    
    // Code to manage Service lifecycle.
    private final ServiceConnection mServiceConnection = new ServiceConnection()
    {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder service)
        {
            mBluetoothLeService = ((BluetoothLeServiceV2.LocalBinder) service).getService();

            Log.d(TAG, "remote onServiceConnected");
            if (!mBluetoothLeService.initialize())
            {
                Log.e(TAG, "Unable to initialize Bluetooth");
                mBluethCallback.OnConnectFail(CONNECT_INIT_FAIL);
            }else
            {
            }            
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName)
        {
            Log.d(TAG, "remote onServiceDisconnected");
            mBluetoothLeService = null;
        }
    };
    
    public void SendData(byte[] dat) {
        if(mTargetConnectInfo == null || mTargetConnectInfo.output == null || mBluetoothLeService == null)
        {
            Log.e(TAG, "BluetoothLeService not ready or output characteristic is null");
            return;
        }
        mTargetConnectInfo.output.setValue(dat);
        mBluetoothLeService.writeCharacteristic(mTargetConnectInfo.address, mTargetConnectInfo.output);
    }
    
    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {

        @Override
        public void onLeScan(BluetoothDevice arg0, int arg1, byte[] arg2)
        {
            // TODO Auto-generated method stub
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = arg0;
            // If it's already paired, skip it, because it's been listed already
            String name = device.getName() + " " + device.getAddress();
            Log.d(TAG, "onLeScan found a ble device! = " + name + " type = " + device.getType() +  " contain = " + mLightDevice.contains(device.getAddress()));
            if ((device.getType() == BluetoothDevice.DEVICE_TYPE_LE || device.getType() == BluetoothDevice.DEVICE_TYPE_UNKNOWN)   
              && !mLightDevice.contains(device.getAddress())) 
            {
                Log.d(TAG, "onLeScan add device " + name);
                mLightDevice.add(device.getAddress());
            }        

        }        
    };
    
    public void BleDiscovery(boolean enable) {
        Log.d(TAG, "BleDiscovery enable = " + enable);
        if(enable)
        {
            IsScanning = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);
            mHandler.postDelayed(new Runnable() {
                
                @Override
                public void run()
                {
                    // TODO Auto-generated method stub
                    Log.d(TAG, "BleDiscovery runnable stop scan");
                    if(IsScanning)
                    {
                	mBluetoothAdapter.stopLeScan(mLeScanCallback);
                	IsScanning = false;
                    }
                }
            }, 5000);        
        }
        else 
        {
            if(IsScanning)
            {
                IsScanning = false;
                mBluetoothAdapter.stopLeScan(mLeScanCallback);             
            }
        }
    }
    
    public boolean ConnectToDevice(String address) 
    {
	BleConnectInfo cinfo = null;
	
	if(address == null)
	{
	    return false;
	}
	
	if(mTargetConnectInfo != null)
	{
	    logd("mTargetConnectInfo is alive, please close it first");
	    return false;
	}
	
	boolean foundF = false;
	if(mConnectInfos == null) loge("mConnectInfos is null");
	for(BleConnectInfo cinfo1: mConnectInfos)
	{
	    if(cinfo1 == null) loge("cinfo1 is null");
	    if(cinfo1.address.equals(address))
	    {
		foundF = true;
		cinfo = cinfo1;
		break;
	    }
	}
	
	if(!foundF)
	{
	    cinfo = new BleConnectInfo(address);
	    mConnectInfos.add(cinfo);
	}

	cinfo.ConnectState = BluetoothLeServiceV2.STATE_CONNECTING;
	ConnectCallable connThread = new ConnectCallable(cinfo);
	ExecutorService es = Executors.newFixedThreadPool(1);
	Future<Integer> future1 = es.submit(connThread);
	
	try {
	    cinfo.ConnectState = future1.get();
	    if(cinfo.ConnectState == BluetoothLeServiceV2.STATE_CONNECTED)
	    {
		mTargetConnectInfo = cinfo;
		mTargetConnectInfo.ConnectState = BluetoothLeServiceV2.STATE_CONNECTED;
		if(mTargetConnectInfo.input == null || mTargetConnectInfo.output == null) {
		    loge("ConnectToDevice BUG!!! input or output charactrics is null");
		    cinfo.ConnectState = BluetoothLeServiceV2.STATE_DISCONNECTING;
		    mBluetoothLeService.disconnect(address);
		    return false;
		}
		return true;
	    }
	    
	} catch (InterruptedException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	} catch (ExecutionException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}
	
	return false;
    }
    
    public boolean ConnectAuto() {
	
	mLightDevice = new ArrayList<String>();
	BleDiscovery(true);
	
	int conn_index = 0;
	
	while(IsScanning || (conn_index < mLightDevice.size()))
	{
	    if(mLightDevice.size() > conn_index)
	    {
		logd("ConnectAuto connect to " + mLightDevice.get(conn_index));
		if(ConnectToDevice(mLightDevice.get(conn_index)))
		{
		    BleDiscovery(false);
		    return true;
		}
		conn_index++;
	    }
	}
	
	return false;
    }
    
    public void DisConnect(String address) {
	BleConnectInfo bConnectInfo = GetConnectInfoByAddress(address);
	bConnectInfo.ConnectState = BluetoothLeServiceV2.STATE_DISCONNECTING;
        mBluetoothLeService.disconnect(address);
    } 
    
    public void DisConnect() {
        mBluetoothLeService.disconnect();
    }   
}
